package com.example.yuque;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class YuqueExporter {

    private static final String YUQUE_API_BASE_URL = "https://www.yuque.com/api/v2";
    private static final String IMAGES_DIR_NAME = "images";
    private static final int MAX_RETRIES = 3;
    private static final long RETRY_DELAY_MS = 1000;

    /**
     * 主入口函数，用于启动语雀文档导出过程。
     *
     * @param args 命令行参数，依次为：用户Token, 知识库ID, 文档短路径, 输出目录
     */
    public static void main(String[] args) {
        if (args.length < 4) {
            System.err.println("Usage: java -jar yuque-export.jar <your_token> <repo_id> <doc_slug> <output_directory>");
            System.err.println("Example: java -jar yuque-export.jar YOUR_TOKEN user/blog intro ./output_directory");
            return;
        }

        String token = args[0];
        String repoId = args[1];
        String docSlug = args[2];
        String outputDir = args[3];

        System.out.println("Starting Yuque document export...");
        System.out.println("Token: ***"); // Mask token for security
        System.out.println("Repo ID: " + repoId);
        System.out.println("Doc Slug: " + docSlug);
        System.out.println("Output Directory: " + outputDir);

        try {
            // 1. 调用语雀API获取文档内容
            String apiUrl = String.format("%s/repos/%s/docs/%s?raw=1", YUQUE_API_BASE_URL, repoId, docSlug);
            String jsonResponse = executeGetRequest(apiUrl, token);

            if (jsonResponse == null) {
                System.err.println("Failed to fetch document from Yuque API after multiple retries.");
                return;
            }

            // 2. 解析JSON响应
            ObjectMapper objectMapper = new ObjectMapper();
            YuqueDocResponse docResponse = objectMapper.readValue(jsonResponse, YuqueDocResponse.class);
            String markdownContent = docResponse.getData().getBody();
            String docTitle = docResponse.getData().getTitle();
            String docUrl = String.format("https://www.yuque.com/%s/%s", repoId, docSlug);
            String createdAt = docResponse.getData().getCreated_at();

            // 3. 创建图片目录
            File imageDir = new File(outputDir, IMAGES_DIR_NAME);
            if (!imageDir.exists()) {
                if (!imageDir.mkdirs()) {
                    System.err.println("Failed to create image directory: " + imageDir.getAbsolutePath());
                    return;
                }
            }
            System.out.println("Image directory created: " + imageDir.getAbsolutePath());

            // 4. 处理图片链接并下载图片
            StringBuffer newMarkdownContent = new StringBuffer();
            Pattern imgPattern = Pattern.compile("!\\[(.*?)\\]\\((https?://(?:[^/]+\\.yuque\\.com)/[^)]+)\\)");
            Matcher matcher = imgPattern.matcher(markdownContent);
            ExecutorService executorService = Executors.newFixedThreadPool(5); // 并发下载图片

            while (matcher.find()) {
                String altText = matcher.group(1);
                String imgUrl = matcher.group(2);
                // 只处理语雀域名下的图片
                if (imgUrl.contains(".yuque.com")) {
                    executorService.submit(() -> {
                        try {
                            String localImagePath = downloadImage(imgUrl, imageDir, token);
                            // 确保在替换时使用正确的相对路径格式
                            String relativePath = "./" + IMAGES_DIR_NAME + "/" + new File(localImagePath).getName();
                            // 构造新的图片引用，保留alt文本
                            matcher.appendReplacement(newMarkdownContent, "![" + altText + "](" + relativePath + ")");
                        } catch (IOException | NoSuchAlgorithmException e) {
                            System.err.println("Failed to download or process image: " + imgUrl + " - " + e.getMessage());
                            // 图片下载失败，保留原始链接
                            matcher.appendReplacement(newMarkdownContent, matcher.group(0));
                        }
                    });
                }
            }
            executorService.shutdown();
            executorService.awaitTermination(1, TimeUnit.HOURS); // 等待所有图片下载完成
            matcher.appendTail(newMarkdownContent);

            // 5. 添加元数据到Markdown头部
            StringBuilder finalMarkdown = new StringBuilder();
            finalMarkdown.append("---\n");
            finalMarkdown.append("title: ").append(docTitle).append("\n");
            finalMarkdown.append("url: ").append(docUrl).append("\n");
            finalMarkdown.append("created_at: ").append(createdAt).append("\n");
            finalMarkdown.append("---\n");
            finalMarkdown.append(newMarkdownContent.toString());

            // 6. 保存Markdown文件
            String safeTitle = docTitle.replaceAll("[\\/:*?\"<>|]", "_") + ".md";
            Path outputFilePath = Paths.get(outputDir, safeTitle);
            Files.write(outputFilePath, finalMarkdown.toString().getBytes(StandardCharsets.UTF_8));
            System.out.println("Markdown file saved: " + outputFilePath.toAbsolutePath());

            System.out.println("Export completed successfully!");

        } catch (IOException e) {
            System.err.println("An I/O error occurred: " + e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.err.println("Image download process was interrupted: " + e.getMessage());
            Thread.currentThread().interrupt();
            e.printStackTrace();
        } catch (Exception e) {
            System.err.println("An unexpected error occurred: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 执行HTTP GET请求。
     *
     * @param url   请求的URL
     * @param token 语雀Personal Access Token
     * @return API响应的字符串内容，如果失败则返回null
     * @throws IOException 当发生网络或I/O错误时
     */
    private static String executeGetRequest(String url, String token) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("X-Auth-Token", token);
        httpGet.setHeader("User-Agent", "YuqueExportTool/1.0"); // 建议添加User-Agent

        int attempt = 0;
        while (attempt < MAX_RETRIES) {
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                int statusCode = response.getStatusLine().getStatusCode();
                HttpEntity entity = response.getEntity();
                String responseBody = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;

                if (statusCode == 200) {
                    return responseBody;
                } else if (statusCode == 401 || statusCode == 403) {
                    System.err.println("Error: Unauthorized or Forbidden. Please check your token and permissions. Status: " + statusCode);
                    System.err.println("Response: " + responseBody);
                    return null; // 认证失败，不重试
                } else if (statusCode == 404) {
                    System.err.println("Error: Document or Repository not found. Please check repo_id and doc_slug. Status: " + statusCode);
                    System.err.println("Response: " + responseBody);
                    return null; // 资源不存在，不重试
                } else if (statusCode == 429) { // API 频率限制
                    System.err.println("API rate limit exceeded. Retrying after " + RETRY_DELAY_MS + "ms... (Attempt " + (attempt + 1) + "/" + MAX_RETRIES + ")");
                    Thread.sleep(RETRY_DELAY_MS * (attempt + 1)); // 增加等待时间
                } else {
                    System.err.println("Request failed with status code: " + statusCode + ". Retrying... (Attempt " + (attempt + 1) + "/" + MAX_RETRIES + ")");
                    System.err.println("Response: " + responseBody);
                    Thread.sleep(RETRY_DELAY_MS);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println("Request retry interrupted: " + e.getMessage());
                return null;
            } catch (IOException e) {
                System.err.println("IOException during request: " + e.getMessage() + ". Retrying... (Attempt " + (attempt + 1) + "/" + MAX_RETRIES + ")");
                if (attempt + 1 >= MAX_RETRIES) throw e; // 最后一次尝试失败则抛出异常
                try {
                    Thread.sleep(RETRY_DELAY_MS);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return null;
                }
            }
            attempt++;
        }
        httpClient.close();
        return null; // 所有重试失败
    }

    /**
     * 下载图片到本地目录。
     *
     * @param imgUrl    图片的URL
     * @param outputDir 图片保存的目录
     * @param token     语雀Token，用于下载需要授权的图片（如果语雀图片链接也需要认证）
     * @return 下载后图片的本地相对路径
     * @throws IOException              当发生网络或I/O错误时
     * @throws NoSuchAlgorithmException 如果MD5算法不可用
     */
    private static String downloadImage(String imgUrl, File outputDir, String token) throws IOException, NoSuchAlgorithmException {
        // 检查是否是已下载的图片（基于文件名哈希）
        String extension = FilenameUtils.getExtension(new URL(imgUrl).getPath());
        if (extension == null || extension.isEmpty()) {
            // 尝试从Content-Type获取，但这里简化为从URL获取，如果URL没有，则默认为png
            extension = "png"; // 默认扩展名
        }
        String hashedName = hashString(imgUrl, "MD5") + "." + extension;
        File imageFile = new File(outputDir, hashedName);

        if (imageFile.exists()) {
            System.out.println("Image already exists (skipped): " + imageFile.getAbsolutePath());
            return "./" + IMAGES_DIR_NAME + "/" + hashedName; // 返回相对路径
        }

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(imgUrl);
        // 如果语雀图片下载也需要Token，则添加
        // httpGet.setHeader("X-Auth-Token", token);
        httpGet.setHeader("User-Agent", "YuqueExportTool/1.0");

        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    try (InputStream inputStream = entity.getContent();
                         FileOutputStream fileOutputStream = new FileOutputStream(imageFile)) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = inputStream.read(buffer)) != -1) {
                            fileOutputStream.write(buffer, 0, bytesRead);
                        }
                        System.out.println("Image downloaded: " + imageFile.getAbsolutePath());
                        return "./" + IMAGES_DIR_NAME + "/" + hashedName; // 返回相对路径
                    } catch (IOException e) {
                        // 下载过程中发生错误，尝试删除不完整的文件
                        if (imageFile.exists()) {
                            imageFile.delete();
                        }
                        throw e; // 重新抛出异常，由上层处理
                    }
                }
            } else {
                System.err.println("Failed to download image " + imgUrl + ". Status: " + statusCode);
                // 尝试读取错误响应体
                HttpEntity errorEntity = response.getEntity();
                if (errorEntity != null) {
                    System.err.println("Error response: " + EntityUtils.toString(errorEntity));
                }
                throw new IOException("Failed to download image " + imgUrl + ". Status: " + statusCode);
            }
        }
        return null; // Should not reach here if download is successful or throws exception
    }

    /**
     * 对字符串进行哈希计算。
     *
     * @param input     输入字符串
     * @param algorithm 哈希算法 (例如 "MD5", "SHA-256")
     * @return 哈希后的字符串
     * @throws NoSuchAlgorithmException 如果指定的算法不可用
     */
    private static String hashString(String input, String algorithm) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance(algorithm);
        byte[] hashedBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte b : hashedBytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    /**
     * 语雀文档API响应的内部数据结构。
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class YuqueDocResponse {
        private YuqueDocData data;

        public YuqueDocData getData() {
            return data;
        }

        public void setData(YuqueDocData data) {
            this.data = data;
        }
    }

    /**
     * 语雀文档的具体数据。
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class YuqueDocData {
        private String title;
        private String body;
        private String created_at;
        // 可以根据需要添加更多字段，如 updated_at, user 等

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

        public String getCreated_at() {
            return created_at;
        }

        public void setCreated_at(String created_at) {
            this.created_at = created_at;
        }
    }
}